package com.thhg.util;

import com.sun.istack.internal.NotNull;
import redis.clients.jedis.util.SafeEncoder;

import java.io.Serializable;
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Zhikang.Peng
 * @version 1.0
 * @email thghh@qq.com
 * @date 2021/6/29 17:01
 */
public class JedisHashMap<K, V> extends AbstractMap<String, String>
        implements Map<String, String>, Cloneable, Serializable {

    private String jedisKey;
    private CustJedis jedis;

    public JedisHashMap(CustJedis jedis, String redisKey) {
        this.jedisKey = redisKey;
        this.jedis = jedis;
    }

    @Override
    public String put(@NotNull String key, String value) {
        String oldVal = get(key);
        jedis.hset(jedisKey, key, value);
        return oldVal;
    }

    @Override
    public String get(@NotNull Object key) {
        return jedis.hget(jedisKey, key.toString());
    }

    @Override
    public String remove(@NotNull Object key) {
        String oldVal = get(key);
        jedis.hdel(jedisKey, key.toString());
        return oldVal;
    }

    @Override
    public int size() {
        Long size = jedis.hlen(jedisKey);
        return size == null ? 0 : size.intValue();
    }

    @Override
    public void clear() {
        jedis.del(jedisKey);
    }

    @Override
    public boolean containsValue(Object value) {
        String v = (value == null ? null : value.toString());
        return jedis.hexists(jedisKey, v);
    }

    @Override
    public boolean containsKey(@NotNull Object key) {
        return get(key) != null;
    }

    @Override
    public void putAll(Map<? extends String, ? extends String> m) {
        final Map<byte[], byte[]> bhash = new HashMap<>(m.size());
        for (final Entry<? extends String, ? extends String> entry : m.entrySet()) {
            bhash.put(SafeEncoder.encode(entry.getKey()), SafeEncoder.encode(entry.getValue()));
        }
        jedis.hmset(SafeEncoder.encode(jedisKey), bhash);
    }

    @Override
    public Set<String> keySet() {
        return jedis.hkeys(jedisKey);
    }

    @Override
    public Collection<String> values() {
        return jedis.hvals(jedisKey);
    }

    @Override
    public int hashCode() {
        return jedisKey.hashCode() + super.hashCode();
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        JedisHashMap result;
        try {
            result = (JedisHashMap) super.clone();
        } catch (CloneNotSupportedException e) {
            // this shouldn't happen, since we are Cloneable
            throw new InternalError(e);
        }
        result.jedisKey = jedisKey;
        result.putAll(this);
        return result;
    }

    public Set<Entry<String, String>> entrySet() {
        return new EntrySet();
    }

    private Node<String, String>[] tableNode() {
        Set<String> keys = keySet();
        Node<String, String>[] tables = new Node[keys.size()];
        int i = 0;
        Node<String, String> p = null;
        for (String key : keys) {
            Node<String, String> node = new Node<>(key, get(key), p);
            tables[i++] = node;
            p = node;
        }
        return tables;
    }

    final class EntrySet extends AbstractSet<Map.Entry<String, String>> {
        public final int size() {
            return JedisHashMap.this.size();
        }

        public final void clear() {
            clear();
        }

        public final Iterator<Entry<String, String>> iterator() {
            return new EntryIterator();
        }

        public final boolean contains(Object o) {
            if (!(o instanceof Map.Entry))
                return false;
            Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
            Object key = e.getKey();
            String val = get(key);
            return val != null && val.equals(e.getValue());
        }

        public final boolean remove(Object o) {
            if (o instanceof Map.Entry) {
                if (contains(o)) {
                    Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
                    JedisHashMap.this.remove(e.getKey());
                    return true;
                }
            }
            return false;
        }

        public final Spliterator<Entry<String, String>> spliterator() {
//            return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0);
            return null;
        }

        public final void forEach(Consumer<? super Entry<String, String>> action) {
            if (action == null)
                throw new NullPointerException();
            int size = JedisHashMap.this.size();
            if (size > 0) {
                Node<String, String>[] tab = tableNode();
                for (int i = 0; i < tab.length; ++i) {
                    for (Node<String, String> e = tab[i]; e != null; e = e.next)
                        action.accept(e);
                }
            }
        }
    }

    @Override
    public void forEach(BiConsumer<? super String, ? super String> action) {
        if (action == null)
            throw new NullPointerException();
        int size = size();
        if (size > 0) {
            entrySet();
        }
    }

    @Override
    public void replaceAll(BiFunction<? super String, ? super String, ? extends String> function) {

    }

    @Override
    public String putIfAbsent(String key, String value) {
        return null;
    }

    @Override
    public boolean remove(Object key, Object value) {
        return false;
    }

    @Override
    public boolean replace(String key, String oldValue, String newValue) {
        return false;
    }

    @Override
    public String replace(String key, String value) {
        return null;
    }

    @Override
    public String computeIfAbsent(String key, Function<? super String, ? extends String> mappingFunction) {
        return null;
    }

    @Override
    public String computeIfPresent(String key, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
        return null;
    }

    @Override
    public String compute(String key, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
        return null;
    }

    @Override
    public String merge(String key, String value, BiFunction<? super String, ? super String, ? extends String> remappingFunction) {
        return null;
    }

    /**
     * Basic hash bin node, used for most entries.  (See below for
     * TreeNode subclass, and in LinkedHashMap for its Entry subclass.)
     */
    static class Node<K, V> implements Map.Entry<K, V> {
        final K key;
        V value;
        Node<K, V> next;

        Node(K key, V value, Node<K, V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }

        public final K getKey() {
            return key;
        }

        public final V getValue() {
            return value;
        }

        public final String toString() {
            return key + "=" + value;
        }

        public final int hashCode() {
            return Objects.hashCode(key) ^ Objects.hashCode(value);
        }

        public final V setValue(V newValue) {
            V oldValue = value;
            value = newValue;
            return oldValue;
        }

        public final boolean equals(Object o) {
            if (o == this)
                return true;
            if (o instanceof Map.Entry) {
                Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
                if (Objects.equals(key, e.getKey()) &&
                        Objects.equals(value, e.getValue()))
                    return true;
            }
            return false;
        }
    }

    /* ------------------------------------------------------------ */
    // iterators

    abstract class HashIterator {
        Node next;        // next entry to return
        Node current;     // current entry
        int index;        // current slot
        Node[] table;

        HashIterator() {
            table = tableNode();
            current = next = null;
            index = 0;
            if (table != null && size() > 0) { // advance to first entry
                do {
                } while (index < table.length && (next = table[index++]) == null);
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Node<String, String> nextNode() {
            Node[] t;
            Node e = next;
            if (e == null)
                throw new NoSuchElementException();
            if ((next = (current = e).next) == null && (t = table) != null) {
                do {
                } while (index < t.length && (next = t[index++]) == null);
            }
            return e;
        }

        public final void remove() {
            Node<String, String> p = current;
            if (p == null)
                throw new IllegalStateException();
            current = null;
            String key = p.key;
            JedisHashMap.this.remove(key);
        }
    }

    final class KeyIterator extends HashIterator
            implements Iterator<String> {
        public final String next() {
            return nextNode().key;
        }
    }

    final class ValueIterator extends HashIterator
            implements Iterator<String> {
        public final String next() {
            return nextNode().value;
        }
    }

    final class EntryIterator extends HashIterator
            implements Iterator<Map.Entry<String, String>> {
        public final Map.Entry<String, String> next() {
            return nextNode();
        }
    }

}
